perm filename DEFSTR.RPG[UP,DOC] blob
sn#603678 filedate 1981-07-31 generic text, type T, neo UTF8
1. Introduction
This document describes the defstruct facility available on
ITS in LIBLSP;STRUCT FASL, on Multics in >udd>Mathlab>Bawden>defstruct
and for LispMachines as part of the regular system.
The features of defstruct differ slightly from place to place.
Thus, part of the purpose of this document is to describe the
differences so that the user can easily convert his defstructs from
one Lisp dialect to another, and to allow him to write compatible
code.
One difficulty that we must deal with immediately is the
question of the ":" macro character. LispMachine users need to know
when a keyword will be interned on the keyword package, and they
expect their documentation to use ":"s in the places that they will be
expected to use them. Because of this ":"s will be used in this
document. Users of defstruct on ITS or Multics will simply have to
pretend that all those ":"s are actually whitespace.
A similar courtesy to the Multics community causes us to
consistently spell all symbol names in lower case.
2. A Simple Example
defstruct is a macro defining macro. The best way to explain
how it works is to show a sample call to defstruct, and then to show
what macros are defined and what each of them does. Sample call to
defstruct:
(defstruct (frob (:type :list))
position
(size 17.)
(name (gensym)))
This form expands into a whole rat's nest of stuff, but the
effect is to define five macros: position, size, name, make-frob and
alter-frob. The definitions of the the first three are easy, they
expand as follows: (I shall use "=>" to indicate macro expansion)
(position x) => (car x)
(size x) => (cadr x)
(name x) => (caddr x)
You can see that defstruct has decided to implement a frob as a list
of three things; its position, its size and its name. The expansion
of make-frob (note that the name is generated by defstruct, there was
no symbol make-frob in the original form) is somewhat harder to
explain, Let's look at a few cases:
(make-frob) => (list nil 17. (gensym))
(make-frob position mars) => (list mars 17. (gensym))
(make-frob name 'fred size 100) => (list nil 100 'fred)
as you can see, make-frob takes a "setq-style" list of part names and
forms, and expands into a call to list that constructs such a frob.
Note that the unspecified parts get defaulted to pieces of code
specified in the original call to defstruct. Note also that the order
of the setq-style argument is ignored in constructing the call to list
(in the example 100 is evaluated before 'fred even though 'fred came
first in the call to make-frob). Care should thus be taken in using
code with side effects within the scope of a make-frob. It is worth
pointing out that the (gensym) is evaluated every time you attempt to
make a new frob (unless you override it), not, as in some defstructs,
just once at the time the defstruct is expanded.
(The explanation of what alter-frob does is delayed until a
later section.)
So now you know how to construct a new frob and how to examine
the parts of a frob, but how do you change the parts of an already
existing frob? The answer is in a macro called "setf". Here are some
examples of how to use setf:
(setf (car x) 47) => (rplaca x 47)
(setf (cadr x) nil) => (rplaca (cdr x) nil)
(setf (get a 'zip) t) => (putprop a t 'zip)
(setf (arraycall t a 1) 'foo) => (store (arraycall t a 1) 'foo)
(setf (symeval foo) bar) => (set foo bar)
(setf foo bar) => (setq foo bar)
(setf (name x) 'bill) => (rplaca (cddr x) 'bill)
The last one is the interesting one. Clearly setf has a lot of
specific knowledge about turning examines into deposits, but how did
it accomplish the last one? Simple! It expanded the macro itself and
got (caddr x), and then operated on that, just as you would have if
you didn't have setf.
setf is not actually a part of the defstruct package, it knows
nothing about defstruct, just how to expand macros and invert
references. However, setf IS necessary to use defstruct. Fortunately
ITS MacLisp and LispMachine Lisp have a setf already, and on Multics a
setf can be found in the same place as defstruct.
And that is just about all there is to defstruct; you now know
enough to use it in your code, but if you want to know about all its
interesting features, then read on.
3. Syntax of defstruct
The general form of a defstruct is:
(defstruct (<name> <option-1> <option-2> ... <option-n>)
<slot-description-1>
<slot-description-2>
...
<slot-description-m>)
<name> must be a symbol, it is used in constructing names
(such as "make-frob") and it is given a defstruct-description property
of a structure that describes the structure completely.
Each <option-i> is either the atomic name of an option, or a
list of the form (<option-name> <arg> . <rest>). (Actually <option-i>
is the same as (<option-i>).) Some options have defaults for <arg>;
some will complain if they are present without an argument; some
options complain if they are present WITH an argument. The
interpretation of <rest> is up to the option in question, but usually
it is expected to be nil.
Each <slot-description-j> is either the atomic name of a slot
in the structure, or a list of the form (<slot-name> <init-code>), or
a list of byte field specifications. (Actually <slot-name> is
equivalent to (<slot-name>).) <init-code> is used by constructor
macros (such as make-frob) to initialize slots not specified in the
call to the constructor. If the <init-code> is not specified then the
slot is initialized to whatever is most convenient. (In the example,
since the structure was a list, nil was used. If the structure had
been a fixnum array then such slots would be filled with zeros.)
A byte field specification looks like: (<field-name> <ppss>)
or (<field-name> <ppss> <init-code>). Note that since a byte field
specification is always a list, a list of byte field specifications
can never be confused with the other cases of a slot description. The
byte field feature of defstruct is explained in detail in section 5.
4. Options to defstruct
This section is a catalog of all options currently known about
by defstruct.
4.1 :type
The :type option specifies what kind of lisp object defstruct
is going to use to implement your structure. The :type option is
illegal without an argument. If the :type option is not specified,
then defstruct will choose an appropriate default (hunks on ITS,
arrays on LispMachines and lists on Multics). It is possible for the
user to teach defstruct new ways to implement structures, the
interested reader is referred to section 8 for more information. Many
useful types have already been defined for the user. A list of these
"built in" types follows:
* =available on ITS
$ =available on Multics
!=available on LispMachines
*$! :list
Uses a list. The default on Multics.
*$! :named-list
Like :list, except the car of each instance of this structure will be
the name symbol of the structure. This is the only "named" structure
type defined on Multics. (see the :named option documented below).
*$! :tree
Creates a binary tree out of conses with the slots as leaves. The
theory is to reduce car-cdring to a minimum.
*$! :array
Uses an array object (NOT a symbol with an array property). The
default on LispMachines. (LispMachine users may want to see the
:make-array option documented below.)
*$! :fixnum-array
Like :array, except it uses a fixnum array and thus your structure can
only contain fixnums. This doesn't really work on LispMachines since
there are no fixnum arrays, but defstruct tries to do something
compatible.
*$! :flonum-array
Analogous to :fixnum-array. LispMachines try to be compatible.
*$! :un-gc-array
Uses a nil type array instead of a t type. This has less hope of
working on a LispMachine than the last two, but the LispMachine tries
anyway.
* :hunk
Builds your structure with an appropriate size hunk. The default on
ITS.
* :named-hunk
Like :hunk, except the car of each instance of this structure will be
the name symbol of the structure. This can be used with the usrhunk
feature of ITS MacLisp to give the user "named structures" (see the
:named option documented below).
* :xhunk
Like :hunk, except the first and second slots are the car and cdr
(respectively) of the structure. Highly random.
* :sfa
Uses an SFA. The constructor macros for this type accept the keywords
:sfa-function and :sfa-name. Their arguments (evaluated, of course)
are used, respectively, as the function and the printed representation
of the sfa. (See also the :sfa-function and :sfa-name options
documented below.)
! :named-array
Uses an array with the named structure bit set and stores the name
symbol of the structure in the first element. (See the :make-array
option documented below.)
! :array-leader
Uses an array with a leader. (See the :make-array option documented
below.)
! :named-array-leader
Uses an array with a leader, sets the named structure bit, and stores
the name symbol in element 1 of the leader. (See the :make-array
option documented below.)
*$! :fixnum
This type allows one to use the byte field feature of defstruct to
deal symbolically with fixnums that aren't actually stored in any
structure at all. Essentially, a structure of type fixnum has exactly
one slot. This allows the operation of retrieving the contents of
that slot to be optimized away into the identity operation. the
reader is referred to section 5 for more information.
! :grouped-array
This type provides a primitive and limited way to place several
instances of a structure side-by-side in an array. The accessor
macros for structures of this type are defined to take a first
argument which should be a fixnum, which is an index into the array of
where this instance of the structure starts. This should be a
multiple of the size of the structure for things to make sense.
Constructor macros for this type of structure take the :times keyword
to tell them how many instances of the structure to make room for in
the array they are making. Only the FIRST instance of the structure
in the array will be initialized. See also the :times and :make-array
options described below.
4.2 :constructor
The :constructor option specifies the name to be given to the
constructor macro. Without an argument, or if the option is not
present, the name defaults to the concatenation of "make-" with the
name of the structure. If the option is given with an argument of
nil, then no constructor is defined. Otherwise the argument is the
name of the constructor to define. Normally the syntax of the
constructor defstruct defines is:
(<constructor-name> <keyword-1> <code-1>
<keyword-2> <code-2>
...
<keyword-n> <code-n>)
Each <keyword-i> must be the name of a slot in the structure (not
necessarily the name of an accessor macro; see the conc-name option),
or one of the special keywords allowed for the particular type of
structure being constructed. For each keyword that is the name of a
slot, the constructor expands into code to make an instance of the
structure using <code-i> to initialize slot <keyword-i>. Unspecified
slots default. For keywords that are not names of slots, the use of
the corresponding code varies, usually it controls some aspect of the
instance being constructed that is not otherwise constrained.
Advanced Feature:
If the :constructor option is given as
(:constructor <name> <arglist>), then instead of making a keyword
driven constructor, defstruct defines a "function style" constructor.
The <arglist> is used to describe what the arguments to the
constructor will be. In the simplest case something like
(:constructor make-foo (a b c)) defines make-foo to be a three
argument constructor macro whose arguments are used to initialize the
slots named a, b and c.
In addition, the keywords &optional, &rest and &aux are
recognized in the argument list. They work in the way you might
expect, but there are a few fine points worthy of explanation:
(:constructor make-foo
(a &optional b (c 'sea) &rest d &aux e (f 'eff)))
This defines make-foo to be a constructor of one or more arguments.
The first argument is used to initialize the a slot. The second
argument is used to initialize the b slot. If there isn't any second
argument, then the default value given in the body of the defstruct
(if given) is used instead. The third argument is used to initialize
the c slot. If there isn't any third argument, then the symbol sea is
used instead. The arguments from the fourth one on are collected into
a list and used to initialize the d slot. If there are three or less
arguments, then nil is placed in the d slot. The e slot IS NOT
INITIALIZED. (It's value will be something convenient like nil or 0
or 0.0.) And finally the f slot is initialized to contain the symbol
eff.
The b and e cases were carefully chosen to allow the user to
specify all possible behaviors. Note that the &aux variables can be
used to completely override the default initializations given in the
body.
Since there is so much freedom in defining constructors this
way, it would be cruel to only allow the :constructor option to be
given once. So, by special dispensation, you are allowed to give the
:constructor option more than once, so that you can define several
different constructors, each with a different syntax.
Note that even these "function style" constructors do not
guarantee that their arguments will be evaluated in the order that you
wrote them.
4.3 :alterant
The :alterant option defines a macro that can be used to change
the value of several slots in a structure together. Without an
argument, or if the option is not present, the name of the alterant
macro defaults to the concatenation of "alter-" with the name of the
structure. If the option is given with an argument of nil, then no
alterant is defined. Otherwise the argument is the name of the
alterant to define. The syntax of the the alterant defstruct defines
is:
(<alterant-name> <code>
<slot-name-1> <code-1>
<slot-name-2> <code-2>
...
<slot-name-n> <code-n>)
<code> should evaluate to an instance of the structure, each <code-i>
is evaluated and the result is made to be the value of slot
<slot-name-i> of that structure. The slots are all altered in
parallel after all code has been evaluated. (Thus you can exchange
the contents to two slots, for example.) Example:
(defstruct (lisp-hacker (:type :list)
:conc-name
:default-pointer
:alterant)
(favorite-macro-package nil)
(unhappy? t)
(number-of-friends 0))
(setq lisp-hacker (make-lisp-hacker))
Now we can perform a transformation:
(alter-lisp-hacker lisp-hacker
favorite-macro-package 'defstruct
number-of-friends 23.
unhappy? nil)
=> ((lambda (G0009)
((lambda (G0011 G0010)
(setf (car G0009) 'defstruct)
(setf (caddr G0009) G0011)
(setf (cadr G0009) G0010))
23.
nil))
lisp-hacker)
Although it appears from this example that your forms will be
evaluated in the order in which you wrote them, this is not
guaranteed.
Alterant macros are particularly good at simultaneously
modifying several byte fields that are allocated from the same word,
they produce better code than you can by simply writing consecutive
setfs. They also produce better code when modifying several slots of
a structure that uses the :but-first option.
4.4 :default-pointer
Normally the accessors are defined to be macros of exactly one
argument (they check!). But if the :default-pointer option is present
then they will accept zero or one argument. When used with one
argument, they behave as before, but given no arguments, they expand
as if they had been called on the argument to the :default-pointer
option. An example is probably called for:
(defstruct (room (:type :tree)
(:default-pointer **current-room**))
(room-name 'y2)
(room-contents-list nil))
Now the accessors expand as follows:
(room-name x) => (car x)
(room-name) => (car **current-room**)
If no argument is given to the :default-pointer option, then the name
of the structure is used as the default pointer. :default-pointer is
most often used in this fashion.
4.5 :conc-name
Frequently all the accessor macros of a structure will want to
have names that begin the same way; usually with the name of the
structure followed by a dash. The :conc-name option allows the user to
specify this prefix. Its argument should be a symbol whose print name
will be concatenated onto the front of the slot names when forming the
accessor macro names. If the argument is not given then the name of
the structure followed by a dash is used. If the :conc-name option is
not present, then no prefix is used. An example illustrates a common
use of the :conc-name option along with the :default-pointer option:
(defstruct (location :default-pointer
:conc-name)
(x 0)
(y 0)
(z 0))
Now if you say
(setq location (make-location x 1 y 34 z 5))
it will be the case that
(location-y)
will return 34. Note well that the name of the slot "y" and the name
of the accessor macro for that slot "location-y" are different.
4.6 :include
The :include option inserts the definition of its argument at
the head of the new structure's definition. In other words, the first
slots of the new structure are equivalent to (i.e. have the same names
as, have the same inits as, etc.) the slots of the argument to the
:include option. The argument to the :include option must be the name
of a previously defined structure of the same type as the new one (if
no type is specified in the new structure then it is defaulted to that
of the included one). It is an error for the :include option to be
present without an argument. Note that :include does not work on
certain types of structures (e.g. structures of type :tree). Note
also that the :conc-name, :default-pointer, :but-first and
:callable-accessors options only apply to the accessors defined in the
current defstruct; no new accessors are defined for the included
slots.
An example:
(defstruct (person (:type :list)
:conc-name)
name
age
sex)
(defstruct (spaceman (:include person)
:default-pointer)
helmet-size
(favorite-beverage 'tang))
Now we can make a spaceman like this:
(setq spaceman (make-spaceman name 'buzz
age 45.
sex t
helmet-size 17.5))
To find out interesting things about spacemen:
(helmet-size) => (cadddr spaceman)
(person-name spaceman) => (car spaceman)
(favorite-beverage x) => (car (cddddr x))
As you can see the accessors defined for the person structure have
names that start with "person-" and they only take one argument. The
names of the accessors for the last two slots of the spaceman
structure are the same as the slot names, but they allow their
argument to be omitted. The accessors for the first three slots of
the spaceman structure are the same as the accessors for the person
structure.
Advanced Feature:
Often, when one structure includes another, the default
initial values supplied by the included structure will be undesirable.
These default initial values can be modified at the time of inclusion
by giving the :include option as:
(:include <name> <new-init-1> ... <new-init-n>).
Each <new-init-i> is either the name of an included slot or of the
form (<included-slot-name> <new-init>). If it is just a slot name,
then in the new structure (the one doing the including) that slot will
have no initial value. If a new initial value is given, then that
code replaces the old initial value code for that slot in the new
structure. The old (included) structure is unmodified.
4.7 :named
This tells defstruct that you desire you structure to be a
"named structure". On ITS this means you want your structure
implemented with a :named-hunk or :named-list. On a LispMachine this
indicates that you desire either a :named-array or a
:named-array-leader or a :named-list. On Multics this indicates that
you desire a :named-list. defstruct bases its decision as to what
named type to use on whatever value you did or didn't give to the
:type option.
It is an error to use this option with an argument.
4.8 :make-array
Available only on LispMachines, this option allows the user to
control those aspects of the array used to implement the structure
that are not otherwise constrained by defstruct (such as the area it
is to be allocated in). The argument to the :make-array option should
be an argument list suitable for the function make-array. Thus
(:make-array (nil 'art-16b)) would request that a 16 bit array be used
to implement this structure. defstruct overrides any element of the
:make-array option that it needs to. For example, if your structure
is of type :array, then defstruct will supply the size of that array
regardless of what you say in the :make-array option.
It is not useful to supply the :make-array option unless
defstruct is using an array to implement your structure, although
defstruct will let you supply it all the same.
Constructor macros for structures implemented as arrays all
allow the keyword :make-array to be supplied. Its argument replaces
any :make-array option supplied in the original defstruct form.
If :make-array isn't used anywhere, then the constructor will
chose appropriate defaults.
4.9 :times
Available only on LispMachines, this option is used by
structures of type :grouped-array to control the number of repetitions of
the structure that will be allocated by the constructor macro. The
constructor macro will also allow :times to be used as a keyword that
will override the value given in the original defstruct form.
If :times is not present anywhere, then the constructor will
only allocate 1 instance of the structure.
4.10 :sfa-function
Available only on ITS, this option allows the user to specify
the function that will be used by the SFA. Its argument should be a
piece of code that evaluates to the desired function. Constructor
macros for this type of structure will take :sfa-function as a keyword
whose argument is also the code to evaluate to get the function,
overriding any supplied in the original defstruct form.
If :sfa-function is not present anywhere, then the constructor
will use the name-symbol of the structure as the function.
4.11 :sfa-name
Available only on ITS, this option allows the user to specify
the object that will be used in the printed representation of the SFA.
Its argument should be a piece of code that evaluates to that object.
Constructor macros for this type of structure will take :sfa-name as a
keyword whose argument is also the code to evaluate to get the object
to use, overriding any supplied in the original defstruct form.
If :sfa-name is not present anywhere, then the constructor
will use the name-symbol of the structure as the function.
4.12 :size-symbol
The :size-symbol option allows a user to specify a symbol
whose value will be the "size" of the structure. The exact meaning of
this varies, but in general this number is the one you would need to
know if you were going to allocate one of these structures yourself.
The symbol will have this value both at compile time and at run time.
If this option is present without an argument, then the name of the
structure is concatenated with "-size" to produce the symbol.
4.13 :size-macro
Similar to :size-symbol. A macro of no arguments is defined
that expands into the size of the structure. The name of this macro
defaults as with :size-symbol.
4.14 :initial-offset
This allows you to tell defstruct to skip over a certain
number of slots before it starts allocating the slots described in the
body. This option requires an argument (which must be a fixnum) which
is the number of slots you want defstruct to skip. To make use of
this option requires that you have some familiarity with how defstruct
is implementing you structure, otherwise you will be unable to make
use of the slots that defstruct has left unused.
4.15 :but-first
This option is best explained by example (mostly because it is
so random!):
(defstruct (head (:type :list)
(:default-pointer person)
(:but-first person-head))
nose
mouth
eyes)
So now the accessors expand like this:
(nose x) => (car (person-head x))
(nose) => (car (person-head person))
The theory is that :but-first's argument will likely be an accessor
from some other structure, and it is never expected that this
structure will be found outside of that slot of that other structure.
(In the example I had in mind that there was a person structure which
had a slot accessed by person-head.) It is an error for the :but-first
option to be used without an argument.
4.16 :callable-accessors
This option controls whether the accessors defined by
defstruct will work as "functional arguments". (As the first argument
to mapcar, for example.) On the LispMachine accessors are callable by
default, but on ITS it is expensive to make this work, so they are
only callable if you ask for it. (Currently on Multics the feature
doesn't work at all...) The argument to this option is nil to
indicate that the feature should be turned off, and non-nil to turn
the feature on. If the option is present with no argument then the
feature is turned on.
4.17 :displace
Normally all of the macros defined by defstruct will be simple
displacing macros. They will use the function displace to rplac the
original form to resemble the expanded code. The :displace option
allows the user to supply some other function to use instead of
displace.
The argument to the :displace option should be a two argument
function that will be called whenever a macroexpansion occurs. The
two arguments will be the original form and the form resulting from
macroexpansion. The value returned by this function will be used for
further evaluation. Note that the function displace is the function
used if the displace option isn't given. The function progn will
cause the macro to be expanded every time.
:displace with no argument is the same as (:displace t) which
is the same as (:displace displace). This is a no-op under the
current defaults. (:displace nil) is the same as (:displace progn).
That is, it turns displacement off.
4.18 :eval-when
Normally the macros defined by defstruct are defined at
eval-time, compile-time and at load-time. This option allows the user
to control this behavior. (:eval-when (:eval :compile)), for example,
will cause the macros to be defined only when the code is running
interpreted and inside the compiler, no trace of defstruct will be
found in compiled code.
4.19 :property
For each structure defined by defstruct, a property list is
maintained for the recording of arbitrary properties about that
structure.
The :property option can be used to give a defstruct an
arbitrary property. (:property <property-name> <value>) gives the
defstruct a <property-name> property of <value>. Neither argument is
evaluated. To access the property list, the user will have to look
inside the defstruct-description structure himself, he is referred to
section 7 for more information.
4.20 <type>
In addition to the options listed above, any currently defined
type (a legal argument to the :type option) can be used as a option.
This is mostly for compatibility with the old LispMachine defstruct.
It allows you to say just <type> when you should be saying
(:type <type>). Use of this feature in new code is discouraged. It is
an error to give an argument to one of these options.
4.21 <other>
Finally, if an option isn't found among those listed above,
defstruct checks the property list of the name of the option to see if
it has a non-nil :defstruct-option property. If is does have such a
property, then if the option was of the form (<option-name> <value>),
it is treated just like (:property <option-name> <value>). That is,
the defstruct is given an <option-name> property of <value>. It is an
error to use such an option without a value.
This provides a primitive way for the user to define his own
options to defstruct. Several of the options listed above are
actually implemented using this mechanism.
5. Byte Fields
The byte field feature of defstruct allows the user to specify
that several slots of his structure are bytes in a fixed point number
stored in one element of the structure. For example, suppose we had
the following structure:
(defstruct (phone-book-entry (:type :list))
name
address
(area-code 617.)
exchange
line-number)
This will work just fine. Except you notice that an area-code and an
exchange are both always less than 1000., and so both can easily fit
in 10. bits, and the line number is always less than 10000. and can
thus fit in 14. bits. Thus you can pack the whole thing in 34. bits
if you have a lisp with 36. bit fixnums. defstruct allows you to do
this as follows:
(defstruct (phone-book-entry (:type :list))
name
address
((area-code 3012 617.)
(exchange 1612)
(line-number 0016)))
The magic numbers 3012, 1612 and 0016 are byte specifiers suitable for
use with the functions ldb and dpb. (These functions don't exist on
Multics yet, which is the reason why nothing in this section will work
there.) Things will expand as follows:
(area-code pbe) => (ldb 3012 (caddr pbe))
(exchange pbe) => (ldb 1612 (caddr pbe))
(make-phone-book-entry
name '|Fred Derf|
address '|259 Octal St.|
exchange ex
line-number 7788.)
=> (list '|Fred Derf| '|259 Octal St.| (dpb ex 1612 115100017154))
(alter-phone-book-entry pbe
exchange ex
line-number ln)
=> ((lambda (G0003)
(setf (caddr G0003)
(dpb ex 1612 (dpb ln 0016 (caddr G0003)))))
pbe)
defstruct tries to be maximally clever about constructing and altering
structures with byte fields.
The byte specifiers are actually pieces of code that are
expected to evaluate to byte specifiers, but defstruct will try and
understand fixnums if you supply them. (In the make-phone-book
example, defstruct was able to make use of its knowledge of the
line-number and area-code byte specifiers to assemble the constant
number 115100017154 and produce code to just deposit in the exchange.)
A nil in the place of the byte specifier code means to define
an accessor for the entire word. So we could say:
(defstruct (phone-book-entry (:type :list))
name
address
((phone-number nil)
(area-code 3012 617.)
(exchange 1612)
(line-number 0016)))
to enable us to do things like:
(setf (phone-number pbe1) (phone-number pbe2)) to cause two entries to
have the same phone numbers. We could also have said just:
((phone-number) ...) in that last defstruct, but the feature of nil
byte specifiers allows you to supply initial values for the entire
slot by saying: ((<name> nil <init>) ...).
Constructor macros initialize words divided into byte fields
as if they were deposited in in the following order:
1) Initializations for the entire word given in the defstruct form.
2) Initializations for the byte fields given in the defstruct form.
3) Initializations for the entire word given in the constructor macro
form.
4) Initializations for the byte fields given in the constructor macro
form.
Alterant macros operate in a similar manner. That is, as if the
entire word was modified first, and then the byte fields were
deposited. Results will be unpredictable in constructing and altering
if byte fields that overlap are given.
6. About Autoloading
This section only applies to ITS and Multics Lisp.
If you look at the property lists of the macros defined by
defstruct you will find that they are all have macro properties of one
of four functions: defstruct-expand-ref-macro,
defstruct-expand-cons-macro, defstruct-expand-alter-macro and
defstruct-expand-size-macro. These functions figure out how to expand
the macro by examining the property list of the car of the form they
are asked to expand. defstruct-expand-ref-macro, for example, looks
for a defstruct-slot property, which should be a cons of the form
(<structure-name> . <slot-name>).
Since the defstruct form only expands into putprops of the
desired functions (instead of actually constructing a full-fledged
definition), loading a compiled file containing a defstruct merely
adds a few properties to some symbols. The run time environment is
not needlessly cluttered with unwanted list structure or subr objects.
If the user thinks he may wish to use any of the macros defined by
defstruct after compiling his file, he need only give the four
expanding functions autoload properties of the name of the file
containing defstruct itself.
For purposes of using defstruct interpreted, the two symbols
defstruct and defstruct-define-type should be given similar autoload
properties. Thus six symbols with autoload properties suffice to make
defstruct appear loaded at all times.
7. The defstruct-description Structure
This section discusses the internal structures used by
defstruct that might be useful to programs that want to interface
to defstruct nicely. The information in this section is also
necessary for anyone who is thinking of defining his own structure
types. LispMachine programmers trying to use the structures defined
in this section will find that they are defined in the
"systems-internals" package.
Whenever the user defines a new structure using defstruct,
defstruct creates an instance of the defstruct-description structure.
This structure can be found as the defstruct-description property of
the name of the structure; it contains such useful information as the
name of structure, the number of slots in the structure, etc.
The defstruct-description structure is defined something like
this: (this is a bowdlerized version of the real thing, I have left
out a lot of things you don't need to know unless you are actually
reading the code)
(defstruct (defstruct-description
(:default-pointer description)
(:conc-name defstruct-description-))
name
size
property-alist
slot-alist
)
The name slot contains the symbol supplied by the user to be
the name of his structure, something like spaceship or
phone-book-entry.
The size slot contains the total number of slots in an
instance of this kind of structure. This is NOT the same number as
that obtained from the :size-symbol or :size-macro options to
defstruct. A named structure, for example, usually uses up an extra
location to store the name of the structure, so the :size-macro option
will get a number one larger than that stored in the defstruct
description.
The property-alist slot contains an alist with pairs of the
form (<property-name> . <property>) containing properties placed there
by the :property option to defstruct or by property names used as
options to defstruct (see sections 4.19 and 4.21).
The slot-alist slot contains an alist of pairs of the form
(<slot-name> . <slot-description>). A <slot-description> is an
instance of the defstruct-slot-description structure. The
defstruct-slot-description structure is defined something like this:
(another bowdlerized defstruct)
(defstruct (defstruct-slot-description
(:default-pointer slot-description)
(:conc-name defstruct-slot-description-))
number
ppss
init-code
ref-macro-name
)
The number slot contains the number of the location of this
slot in an instance of the structure. Locations are numbered
starting with 0, and continuing up to one less than the size of the
structure. The actual location of the slot is determined by the
reference consing code associated with the type of the structure. See
section 8.
The ppss slot contains the byte specifier code for this slot if
this slot is a byte field of its location. If this slot is the entire
location, then the ppss slot contains nil.
The init-code slot contains the initialization code supplied
for this slot by the user in his defstruct form. If there is no
initialization code for this slot then the init-code slot contains the
symbol named "%%defstruct-empty%%".
The ref-macro-name slot contains the symbol that is defined as
a macro that expands into a reference to this slot.
8. Extensions to defstruct
The macro defstruct-define-type can be used to teach defstruct
about new types it can use to implement structures.
8.1 An Example
Let us start by examining a sample call to
defstruct-define-type. This is how the :list type of structure might
have been defined:
(defstruct-define-type :list
(:cons (initialization-list description keyword-options) :list
(cons 'list initialization-list))
(:ref (slot-number description argument)
(list 'nth slot-number argument)))
This is the minimal example. We have provided defstruct with two
pieces of code, one for consing up forms to construct instances of the
structure, the other to cons up forms to reference various elements of
the structure.
From the example we can see that the constructor consing code
is going to be run in an environment where the variable
"initialization-list" is bound to a list which is the initializations
to the slots of the structure arranged in order. The variable
"description" will be bound to the defstruct-description structure for
the structure we are consing a constructor for (see section 7). The
binding of the variable "keyword-options" will be described later.
Also the symbol :list appears after the argument list, this conveys
some information to defstruct about how the constructor consing code
wants to get called.
The reference consing code gets run with the variable
"slot-number" bound to the number of the slot that is to be referenced
and the variable "argument" bound to the code that appeared as the
argument to the accessor macro. The variable "description" is again
bound to the appropriate defstruct-description structure.
This simple example probably tells you enough to be able to go
ahead and implement other structure types, but more details follow.
8.2 Syntax of defstruct-define-type
The syntax of defstruct-define-type is:
(defstruct-define-type <type>
<option-1>
...
<option-n>)
where each <option-i> is either the symbolic name of an option or a
list of the form (<option-name> . <rest>). (Actually <option-name> is
the same as (<option>).) Different options interpret <rest> in
different ways.
The symbol <type> is given a defstruct-type-description
property of a structure that describes the type completely.
8.3 Options to defstruct-define-type
This section is a catalog of all the options currently know
about by defstruct-define-type.
8.3.1 :cons
The :cons option to defstruct-define-type is how the user
supplies defstruct with the necessary code that it needs to cons up a
form that will construct an instance of a structure of this type.
The :cons option has the syntax:
(:cons (<inits> <description> <keywords>) <flavor>
<body>)
<body> is some code that should construct and return a piece
of code that will construct, initialize and return an instance of a
structure of this type.
The symbol <inits> will be bound to the code that the
constructor conser should use to initialize the slots of the
structure. The exact form of this argument is determined by the
symbol <flavor>. There are currently two flavors of initialization.
There is the :list flavor, where <inits> is bound to a list of
initializations, in the correct order, with nils in uninitialized
slots. And there is the :alist flavor, where <inits> is bound to an
alist with pairs of the form (<slot-number> . <init-code>).
The symbol <description> will be bound to the instance of the
defstruct-description structure (described in section 7) that
defstruct maintains for this particular structure. This is so that
the constructor conser can find out such things as the total size of
structure it is supposed to create.
The symbol <keywords> will be bound to a alist with pairs of
the form (<keyword> . <value>), where each <keyword> was a keyword
supplied to the constructor macro that wasn't the name of a slot, and
<value> was the "code" that followed the keyword. See sections 8.3.5
and 4.2.
It is an error not to supply the :cons option to
defstruct-define-type.
8.3.2 :ref
The :ref option to defstruct-define-type is how the user
supplies defstruct with the necessary code that it needs to cons up a
form that will reference an instance of a structure of this type.
The :ref option has the syntax:
(:ref (<number> <description> <arg-1> ... <arg-n>)
<body>)
<body> is some code that should construct and return a piece
of code that will reference an instance of a structure of this type.
The symbol <number> will be bound to the location of the slot
that the is to be referenced. This is the same number that is found
in the number slot of the defstruct-slot-description structure
(section 7).
The symbol <description> will be bound to the instance of the
defstruct-description structure that defstruct maintains for this
particular structure.
The symbols <arg-i> are bound to the forms supplied to the
accessor as arguments. Normally there should be only one of these.
The LAST argument is the one that will be defaulted by the
:default-pointer option (section 4.4). defstruct will check that the
user has supplied exactly n arguments to the accessor macro before
calling the reference consing code.
It is an error not to supply the :ref option to
defstruct-define-type.
8.3.3 :overhead
The :overhead option to defstruct-define-type is how the user
declares to defstruct that the implementation of this particular type
of structure "uses up" some number of slots locations in the object
actually constructed. This option is used by various "named" types of
structures that store the name of the structure in one location.
The syntax of :overhead is: (:overhead <n>) where <n> is a
fixnum that says how many locations of overhead this type needs.
This number is only used by the :size-macro and :size-symbol
options to defstruct. (See sections 4.12 and 4.13.)
8.3.4 :named
The :named option to defstruct-define-type controls the use of
the :named option to defstruct. With no argument the :named option
means that this type is an acceptable "named structure". With an
argument, as in (:named <type-name>), the symbol <type-name> should be
that name of some other structure type that defstruct should use if
someone asks for the named version of this type. (For example in the
definition of the :list type the :named option is used like this:
(:named :named-list).)
8.3.5 :keywords
The :keywords option to defstruct-define-type allows the user
to define additional constructor keywords for this type of structure.
(For example the :make-array constructor keyword for structures of
type :array on LispMachines.) The syntax is:
(:keywords <keyword-1> ... <keyword-n>) where each <keyword-i> is a
symbol that the constructor conser expects to find in the <keywords>
alist (explained above).
8.3.6 :defstruct
The :defstruct option to defstruct-define-type allows the user
to run some code and return some forms as part of the expansion of the
defstruct macro.
The :defstruct option has the syntax:
(:defstruct (<description>)
<body>)
<body> is a piece of code that will be run whenever defstruct
is expanding a defstruct form that defines a structure of this type.
The symbol <description> will be bound to the instance of the
defstruct-description structure that defstruct maintains for this
particular structure.
The value returned by the :defstruct option should be a LIST
of forms to be included with those that the defstruct expands into.
Thus, if you only want to run some code at defstruct expand time, and
you don't want to actually output any additional code, then you should
be careful to return nil from the code in this option.βββ